package run.halo.app.repository;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.NonNull;
import run.halo.app.model.entity.Site;
import run.halo.app.model.enums.SiteStatus;
import run.halo.app.repository.base.BaseRepository;

/**
 * Site repository.
 *
 * @author meazty
 */
public interface SiteRepository
    extends BaseRepository<Site, Integer>, JpaSpecificationExecutor<Site> {

    /**
     * Counts visits. (Need to be overridden)
     *
     * @return total visits
     */
    @Query("select sum(s.visits) from Site s")
    Long countVisit();

    /**
     * Counts likes. (Need to be overridden)
     *
     * @return total likes
     */
    @Query("select sum(s.likes) from Site s")
    Long countLike();

    /**
     * Finds sites by status and pageable.
     *
     * @param status site status must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatus(@NonNull SiteStatus status, @NonNull Pageable pageable);

    /**
     * Finds sites by status.
     *
     * @param status site status must not be null
     * @return a list of site
     */
    @NonNull
    List<Site> findAllByStatus(@NonNull SiteStatus status);

    /**
     * Finds sites by status.
     *
     * @param status site status must not be null
     * @param sort sort info must not be null
     * @return a list of site
     */
    @NonNull
    List<Site> findAllByStatus(@NonNull SiteStatus status, @NonNull Sort sort);

    /**
     * Finds all site by status and create time before.
     *
     * @param status status must not be null
     * @param createTime create time must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndCreateTimeBefore(@NonNull SiteStatus status,
        @NonNull Date createTime, @NonNull Pageable pageable);

    /**
     * Finds all site by status and create time after.
     *
     * @param status status must not be null
     * @param createTime create time must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndCreateTimeAfter(@NonNull SiteStatus status,
        @NonNull Date createTime, @NonNull Pageable pageable);

    /**
     * Finds all site by status and edit time before.
     *
     * @param status status must not be null
     * @param updateTime edit time must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndUpdateTimeBefore(@NonNull SiteStatus status,
        @NonNull Date updateTime,
        @NonNull Pageable pageable);

    /**
     * Finds all site by status and edit time after.
     *
     * @param status status must not be null
     * @param updateTime edit time must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndUpdateTimeAfter(@NonNull SiteStatus status,
        @NonNull Date updateTime,
        @NonNull Pageable pageable);

    /**
     * Finds all site by status and visits before.
     *
     * @param status status must not be null
     * @param visits visits must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndVisitsBefore(@NonNull SiteStatus status, @NonNull Long visits,
        @NonNull Pageable pageable);

    /**
     * Finds all site by status and visits after.
     *
     * @param status status must not be null
     * @param visits visits must not be null
     * @param pageable page info must not be null
     * @return a page of site
     */
    @NonNull
    Page<Site> findAllByStatusAndVisitsAfter(@NonNull SiteStatus status, @NonNull Long visits,
        @NonNull Pageable pageable);

    /**
     * Gets site by slug and status.
     *
     * @param slug slug must not be blank
     * @param status status must not be null
     * @return an optional site
     */
    @NonNull
    Optional<Site> getBySlugAndStatus(@NonNull String slug, @NonNull SiteStatus status);

    /**
     * Gets site by id and status.
     *
     * @param id id must not be blank
     * @param status status must not be null
     * @return an optional site
     */
    @NonNull
    Optional<Site> getByIdAndStatus(@NonNull Integer id, @NonNull SiteStatus status);


    /**
     * Counts sites by status and type.
     *
     * @param status status
     * @return sites count
     */
    long countByStatus(@NonNull SiteStatus status);

    /**
     * Determine if the slug exists.
     *
     * @param slug slug must not be null.
     * @return true or false.
     */
    boolean existsBySlug(@NonNull String slug);

    /**
     * Determine if the slug exists.
     *
     * @param id site id must not be null.
     * @param slug slug must not be null.
     * @return true or false.
     */
    boolean existsByIdNotAndSlug(@NonNull Integer id, @NonNull String slug);

    /**
     * Get site by slug
     *
     * @param slug site slug
     * @return site or empty
     */
    Optional<Site> getBySlug(@NonNull String slug);

    /**
     * Find by site year and month and slug.
     *
     * @param year site create year
     * @param month site create month
     * @param slug site slug
     * @return a optional of site
     */
    @Query("select site from Site site where year(site.createTime) = :year and month(site"
        + ".createTime) = :month and site.slug = :slug")
    Optional<Site> findBy(@Param("year") Integer year, @Param("month") Integer month,
        @Param("slug") String slug);

    /**
     * Find by site year and slug.
     *
     * @param year site create year
     * @param slug site slug
     * @return a optional of site
     */
    @Query("select site from Site site where year(site.createTime) = :year and site.slug = :slug")
    Optional<Site> findBy(@Param("year") Integer year, @Param("slug") String slug);


    /**
     * Find by site year and month and slug and status.
     *
     * @param year site create year
     * @param month site create month
     * @param slug site slug
     * @param status site status
     * @return a optional of site
     */
    @Query("select site from Site site where year(site.createTime) = :year and month(site"
        + ".createTime) = :month and site.slug = :slug and site.status = :status")
    Optional<Site> findBy(@Param("year") Integer year, @Param("month") Integer month,
        @Param("slug") String slug, @Param("status") SiteStatus status);

    /**
     * Find by site year and month and day and slug.
     *
     * @param year site create year
     * @param month site create month
     * @param day site create day
     * @param slug site slug
     * @return a optional of site
     */
    @Query("select site from Site site where year(site.createTime) = :year and month(site"
        + ".createTime) = :month and day(site.createTime) = :day and site.slug = :slug")
    Optional<Site> findBy(@Param("year") Integer year, @Param("month") Integer month,
        @Param("day") Integer day, @Param("slug") String slug);

    /**
     * Find by site year and month and day and slug and status.
     *
     * @param year site create year
     * @param month site create month
     * @param day site create day
     * @param slug site slug
     * @param status site status
     * @return a optional of site
     */
    @Query("select site from Site site where year(site.createTime) = :year and month(site"
        + ".createTime) = :month and day(site.createTime) = :day and site.slug = :slug and site"
        + ".status = :status")
    Optional<Site> findBy(@Param("year") Integer year, @Param("month") Integer month,
        @Param("day") Integer day, @Param("slug") String slug, @Param("status") SiteStatus status);

    /**
     * Updates site visits.
     *
     * @param visits visit delta
     * @param siteId site id must not be null
     * @return updated rows
     */
    @Modifying
    @Query("update Site s set s.visits = s.visits + :visits where s.id = :siteId")
    int updateVisit(@Param("visits") long visits, @Param("siteId") @NonNull Integer siteId);

    /**
     * Updates site likes.
     *
     * @param likes likes delta
     * @param siteId site id must not be null
     * @return updated rows
     */
    @Modifying
    @Query("update Site s set s.likes = s.likes + :likes where s.id = :siteId")
    int updateLikes(@Param("likes") long likes, @Param("siteId") @NonNull Integer siteId);

    /**
     * Updates site status by site id.
     *
     * @param status site status must not be null.
     * @param siteId site id must not be null.
     * @return updated rows.
     */
    @Modifying
    @Query("update Site s set s.status = :status where s.id = :siteId")
    int updateStatus(@Param("status") @NonNull SiteStatus status,
        @Param("siteId") @NonNull Integer siteId);

}
